package Lexico;

import Utils.TipoProducao;
import java.io.IOException;
import java.io.PushbackReader;
import java.util.ArrayList;

import Utils.Token;


public class AnalisadorLexico {

    private final PushbackReader caractere;
    private ArrayList<Token> tokens;
    private int linha, coluna;

    private enum Estados {

        INICIAL,
        DOIS_PONTOS,
        ATRIBUICAO,
        IGUAL,
        MENOR_QUE,
        MENOR_OU_IGUAL,
        IGUAL_OU_MAIOR,
        MAIOR_QUE,
        DIFERENTE,
        SOMA,
        SUBTRACAO,
        MULTIPLICACAO,
        BARRA,
        PONTO_VIRGULA,
        VIRGULA,
        ERRO,
        IDENTIFICADOR,
        ABRE_PARENTESE,
        FECHA_PARENTESE,
        ABRE_CHAVES,
        FECHA_CHAVES,
        ABRE_COMENTARIO,
        FECHA_COMENTARIO,
        COMENTARIO_PARENTESE,
        COMENTARIO_CHAVES,
        DIGITO,
        EXPOENTE,
        PONTO_NUMERICO,
        ABRE_ASPAS_SIMPLES,
        ABRE_ASPAS_DUPLAS,
        FECHA_ASPAS_SIMPLES,
        FECHA_ASPAS_DUPLAS,
        LITERAL, SINAL_EXPOENTE, QUOTED_STRING, VALOR_QUOTED
    };

    public AnalisadorLexico(PushbackReader caractere) {
        this.tokens = new ArrayList<>();
        this.caractere = caractere;
    }

    public ArrayList<Token> analisar() throws IOException {
        String valor = "";
        Estados estado = Estados.INICIAL;
        this.coluna = 0;
        this.linha = 1;
        int carac = caractere.read();
        while (carac >= 0) {
            if (estado != Estados.INICIAL) {
                valor = valor + (char) carac;
                carac = getCaractere();
            }

            switch (estado) {
                case INICIAL:
                    valor = "";
                    switch (carac) {
                        case ':':
                            estado = Estados.DOIS_PONTOS;
                            break;
                        case '\n':
                            carac = getCaractere();
                            this.linha++;
                            this.coluna = 0;
                            break;
                        case 13:
                        case '\t':
                        case ' ':
                            carac = getCaractere();
                            break;
                        case '=':
                            estado = Estados.IGUAL;
                            break;
                        case '<':
                            estado = Estados.MENOR_QUE;
                            break;
                        case '>':
                            estado = Estados.MAIOR_QUE;
                            break;
                        case '+':
                            estado = Estados.SOMA;
                            break;
                        case '-':
                            estado = Estados.SUBTRACAO;
                            break;
                        case '*':
                            estado = Estados.MULTIPLICACAO;
                            break;
                        case '/':
                            estado = Estados.BARRA;
                            break;
                        case ';':
                            estado = Estados.PONTO_VIRGULA;
                            break;
                        case ',':
                            estado = Estados.VIRGULA;
                            break;
                        case '{':
                            estado = Estados.ABRE_CHAVES;
                            break;
                        case '}':
                            estado = Estados.FECHA_CHAVES;
                            break;
                        case '(':
                            estado = Estados.ABRE_PARENTESE;
                            break;
                        case ')':
                            estado = Estados.FECHA_PARENTESE;
                            break;
                        case 34: //Aspas Duplas
                            estado = Estados.ABRE_ASPAS_DUPLAS;
                            break;
                        case 39: //ASpas Simples
                            estado = Estados.ABRE_ASPAS_SIMPLES;
                            break;
                        default:
                            if ((carac >= 65 && carac <= 90) || (carac >= 97 && carac <= 122) || carac == 95) {
                                estado = Estados.IDENTIFICADOR;
                                break;
                            } else if ((carac >= 48 && carac <= 57) || carac == '.') {
                                estado = Estados.DIGITO;
                            } else {
                                estado = Estados.ERRO;
                                break;
                            }
                    }

                    break;
                case ATRIBUICAO:
                    this.tokens.add(new Token("GERAL -> Atribuicao", valor, TipoProducao.DOIS_PONTOS_IGUAL, this.linha, this.coluna));
                    estado = Estados.INICIAL;
                    break;
                case DOIS_PONTOS:
                    switch (carac) {
                        case '=':
                            estado = Estados.ATRIBUICAO;
                            break;
                        default:
                            //ADRIANO Não existe dois pontos na gramtica
                            this.tokens
                                    .add(new Token("GERAL -> Dois Pontos", valor, TipoProducao.DOIS_PONTOS, this.linha, this.coluna));
                            estado = Estados.INICIAL;
                            break;
                    }
                    break;
                case IGUAL:
                    switch (carac) {
                        case '>':
                            estado = Estados.IGUAL_OU_MAIOR;
                            break;
                        default:
                            this.tokens.add(new Token("RELOP -> Igualdade", valor, TipoProducao.IGUAL, this.linha, this.coluna));
                            estado = Estados.INICIAL;
                            break;
                    }
                    break;
                case IGUAL_OU_MAIOR:
                    //ADRIANO Mairo igual esta faltando na gramatica
                    this.tokens.add(new Token("RELOP -> Igual ou Maior", valor, TipoProducao.MAIOR, this.linha, this.coluna));
                    estado = Estados.INICIAL;
                    break;
                case MENOR_QUE:
                    switch (carac) {
                        case '=':
                            estado = Estados.MENOR_OU_IGUAL;
                            break;
                        case '>':
                            estado = Estados.DIFERENTE;
                            break;
                        default:
                            this.tokens.add(new Token("RELOP -> Menor que", valor, TipoProducao.MENOR, this.linha, this.coluna));
                            estado = Estados.INICIAL;
                            break;
                    }
                    break;

                case MENOR_OU_IGUAL:
                    this.tokens.add(new Token("RELOP -> Menor ou igual", valor, TipoProducao.MENOR_IGUAL, this.linha, this.coluna));
                    estado = Estados.INICIAL;
                    break;

                case MAIOR_QUE:
                    this.tokens.add(new Token("RELOP -> Maior Que", valor, TipoProducao.MAIOR, this.linha, this.coluna));
                    estado = Estados.INICIAL;
                    break;

                case DIFERENTE:
                    this.tokens.add(new Token("RELOP -> DIferente", valor, TipoProducao.DIFERENTE, this.linha, this.coluna));
                    estado = Estados.INICIAL;
                    break;

                case SOMA:
                    this.tokens.add(new Token("ADDOP -> Soma", valor, TipoProducao.SOMAR, this.linha, this.coluna));
                    estado = Estados.INICIAL;
                    break;

                case SUBTRACAO:
                    this.tokens.add(new Token("ADDOP -> Subtracao", valor, TipoProducao.SUBRITAIR, this.linha, this.coluna));
                    estado = Estados.INICIAL;
                    break;

                case MULTIPLICACAO:
                    this.tokens.add(new Token("MULTOP -> Multiplicacao", valor, TipoProducao.ASTERISCO, this.linha, this.coluna));
                    estado = Estados.INICIAL;
                    break;

                case BARRA:
                    this.tokens.add(new Token("MULTOP -> Barra", valor, TipoProducao.BARRA, this.linha, this.coluna));
                    estado = Estados.INICIAL;
                    break;

                case PONTO_VIRGULA:
                    // ADRIANO Não existe na gramatica
                    this.tokens.add(new Token("GERAL -> Ponto e Virgula", valor, TipoProducao.PONTO_VIRGULA, this.linha, this.coluna));
                    estado = Estados.INICIAL;
                    break;

                case VIRGULA:
                    // ADRIANO Não existe  na gramatica
                    this.tokens.add(new Token("GERAL -> Virgula", valor, TipoProducao.VIRGULA, this.linha, this.coluna));
                    estado = Estados.INICIAL;
                    break;

                case ERRO:
                    // Não existe tipo erro na gramatica
                    this.tokens.add(new Token("ERRO", valor, TipoProducao.ERRO, this.linha, this.coluna));
                    estado = Estados.INICIAL;
                    break;

                case IDENTIFICADOR:
                    switch (carac) {
                        case ':':
                        case '\n':
                        case ' ':
                        case '=':
                        case '<':
                        case '>':
                        case '+':
                        case '-':
                        case '*':
                        case '/':
                        case ';':
                        case ',':
                        case '\t':
                        case '{':
                        case '}':
                        case '(':
                        case ')':
                        case '.':
                        case 13:
                        case 34: //Aspas Duplas
                        case 39: //ASpas Simples
                            recolhecePalavrasReservadas(valor, this.linha, this.coluna);
                            //this.tokens.add(new Token("IDENTIFICADOR ->", valor, TipoProducao.IDENT, this.linha, this.coluna));
                            estado = Estados.INICIAL;
                            break;
                        default:
                            break;
                    }
                    break;
                case ABRE_CHAVES:
                    //ADRIANO não existe na Gramatica
                    this.tokens.add(new Token("ABRE CHAVES ->", valor, TipoProducao.ABRE_CHAVES, this.linha, this.coluna));
                    estado = Estados.COMENTARIO_CHAVES;
                    valor = "";
                    break;
                case FECHA_CHAVES:
                    //ADRIANO não existe na Gramatica
                    this.tokens.add(new Token("FECHA CHAVES ->", valor, TipoProducao.FECHA_CHAVES, this.linha, this.coluna));
                    estado = Estados.INICIAL;
                    break;
                case ABRE_PARENTESE:
                    switch (carac) {
                        case '*':
                            estado = Estados.ABRE_COMENTARIO;
                            break;
                        default:
                            this.tokens.add(new Token("ABRE PARENTESE ->", valor, TipoProducao.ABRE_PARENTESE, this.linha, this.coluna));
                            estado = Estados.INICIAL;
                            break;
                    }
                    break;
                case FECHA_PARENTESE:
                    this.tokens.add(new Token("FECHA PARENTESE ->", valor, TipoProducao.FECHA_PARENTESE, this.linha, this.coluna));
                    estado = Estados.INICIAL;
                    break;
                case ABRE_COMENTARIO:
                    this.tokens.add(new Token("ABRE COMENTARIO ->", valor, TipoProducao.ABRE_CHAVES, this.linha, this.coluna));
                    estado = Estados.COMENTARIO_PARENTESE;
                    valor = "";
                    break;
                case COMENTARIO_CHAVES:
                    switch (carac) {
                        case '}':
                            this.tokens.add(new Token("COMENTARIO ->", valor, TipoProducao.FECHA_CHAVES, this.linha, this.coluna));
                            estado = Estados.INICIAL;
                            break;
                        default:
                            break;
                    }
                    break;
                case COMENTARIO_PARENTESE:
                    switch (carac) {
                        case '*':
                            estado = Estados.FECHA_COMENTARIO;
                            break;
                        default:
                            break;
                    }
                    break;
                //GAMBIARRA MODE ON
                case FECHA_COMENTARIO:
                    switch (carac) {
                        case ')':
                            this.tokens.add(new Token("COMENTARIO ->",
                                    valor.substring(0, valor.length() - 1), TipoProducao.COMENTARIO, this.linha, this.coluna));
                            this.tokens.add(new Token("FECHA COMENTARIO ->",
                                    valor.substring(valor.length() - 1, valor.length()) + (char) carac, TipoProducao.FECHA_CHAVES, this.linha, this.coluna));
                            carac = getCaractere();
                            estado = Estados.INICIAL;
                            break;
                        default:
                            estado = Estados.COMENTARIO_PARENTESE;
                            break;
                    }
                    break;

                case DIGITO:
                    switch (carac) {
                        case '-':
                        case '+':
                            estado = Estados.SINAL_EXPOENTE;
                            break;
                        case 'E':
                        case 'e':
                            estado = Estados.EXPOENTE;
                            break;
                        case '.':
                            estado = Estados.PONTO_NUMERICO;
                            break;
                        default:
                            if (!(carac >= 48 && carac <= 57)) {
                                this.tokens.add(new Token("Digito ->", valor, TipoProducao.IDENTIFIER, this.linha, this.coluna));
                                estado = Estados.INICIAL;
                            }
                            break;
                    }
                    break;
                case EXPOENTE:
                    if (!(carac >= 48 && carac <= 57)) {
                        this.tokens.add(new Token("Digito ->", valor, TipoProducao.IDENTIFIER, this.linha, this.coluna));
                        estado = Estados.INICIAL;
                    }
                    break;
                case SINAL_EXPOENTE:
                    switch (carac) {
                        case 'E':
                        case 'e':
                            estado = Estados.EXPOENTE;
                            break;
                    }
                    break;
                case PONTO_NUMERICO:
                    switch (carac) {
                        case '-':
                        case '+':
                            estado = Estados.SINAL_EXPOENTE;
                            break;
                        case 'E':
                        case 'e':
                            estado = Estados.EXPOENTE;
                            break;
                        default:
                            if (!(carac >= 48 && carac <= 57)) {
                                this.tokens.add(new Token("Digito ->", valor, TipoProducao.IDENTIFIER, this.linha, this.coluna));
                                estado = Estados.INICIAL;
                            }
                            break;
                    }
                    break;
                case ABRE_ASPAS_SIMPLES:
                    this.tokens.add(new Token("ASPAS SIMPLES ->", valor, TipoProducao.ASPAS_SIMPLES, this.linha, this.coluna));
                    valor = "";
                    estado = Estados.LITERAL;
                    break;
                case ABRE_ASPAS_DUPLAS:
                    this.tokens.add(new Token("ASPAS DUPLAS ->", valor, TipoProducao.ASPAS_DUPLAS, this.linha, this.coluna));
                    estado = Estados.INICIAL;
                    break;
                case FECHA_ASPAS_SIMPLES:
                    this.tokens.add(new Token("ASPAS SIMPLES ->", valor, TipoProducao.ASPAS_SIMPLES, this.linha, this.coluna));
                    estado = Estados.INICIAL;
                    break;
                case QUOTED_STRING:
                    switch (carac) {
                        case 39: //Aspas Duplas
                            estado = Estados.VALOR_QUOTED;
                            break;
                        default:
                            this.tokens.add(new Token("LITERAL ->",
                                    valor.substring(0, valor.length() - 1), TipoProducao.LIETRAL, this.linha, this.coluna));
                            this.tokens.add(new Token("ASPAS SIMPLES ->",
                                    valor.substring(valor.length() - 1, valor.length()) + (char) carac, TipoProducao.ASPAS_SIMPLES, this.linha, this.coluna));
                            carac = getCaractere();
                            estado = Estados.INICIAL;
                            break;
                    }
                    break;
                case VALOR_QUOTED:
                    switch (carac) {
                        case 39: //Aspas Duplas
                            this.tokens.add(new Token("LITERAL ->", valor, TipoProducao.LIETRAL, this.linha, this.coluna));
                            estado = Estados.FECHA_ASPAS_SIMPLES;
                            valor = "";
                            break;
                        default:
                            estado = Estados.LITERAL;
                            break;
                    }
                    break;
                case FECHA_ASPAS_DUPLAS:
                    this.tokens.add(new Token("ASPAS DUPLAS ->", valor, TipoProducao.ASPAS_DUPLAS, this.linha, this.coluna));
                    valor = "";
                    estado = Estados.INICIAL;
                    break;
                case LITERAL:
                    switch (carac) {
                        case 34: //Aspas Duplas
                            this.tokens.add(new Token("LITERAL ->", valor, TipoProducao.LIETRAL, this.linha, this.coluna));
                            estado = Estados.FECHA_ASPAS_DUPLAS;
                            valor = "";
                            break;
                        case 39://Aspas Simples
                            estado = Estados.QUOTED_STRING;
                            break;
                    }
                    break;
            }
        }

        if (valor != "") {
            if (estado == Estados.IDENTIFICADOR) {
                recolhecePalavrasReservadas(valor, linha, coluna);
            }
        }
        caractere.close();
        return this.tokens;

    }

    private int getCaractere() throws IOException {
        this.coluna++;
        return caractere.read();
    }

    private void recolhecePalavrasReservadas(String valor, int linha, int coluna) {
        switch (valor.trim()) {
            case "program":
                this.tokens.add(new Token("PROGRAM ->", valor, TipoProducao.PROGRAM, linha, coluna));
                break;
            case "end":
                this.tokens.add(new Token("END ->", valor, TipoProducao.END, linha, coluna));
                break;
            case "procedure":
                this.tokens.add(new Token("PROCEDURE ->", valor, TipoProducao.PROCEDURE, linha, coluna));
                break;
            case "function":
                this.tokens.add(new Token("FUNCTION ->", valor, TipoProducao.FUNCTION, linha, coluna));
                break;
            case "var":
                this.tokens.add(new Token("VAR ->", valor, TipoProducao.VAR, linha, coluna));
                break;
            case "integer":
                this.tokens.add(new Token("INTEGER ->", valor, TipoProducao.INTEGER, linha, coluna));
                break;
            case "real":
                this.tokens.add(new Token("REAL ->", valor, TipoProducao.REAL, linha, coluna));
                break;
            case "char":
                this.tokens.add(new Token("CHAR ->", valor, TipoProducao.CHAR, linha, coluna));
                break;
            case "boolean":
                this.tokens.add(new Token("BOOLEAN ->", valor, TipoProducao.BOOLEAN, linha, coluna));
                break;
            case "alfa":
                this.tokens.add(new Token("ALFA ->", valor, TipoProducao.ALFA, linha, coluna));
                break;
            case "begin":
                this.tokens.add(new Token("BEGIN ->", valor, TipoProducao.BEGIN, linha, coluna));
                break;
            case "if":
                this.tokens.add(new Token("IF ->", valor, TipoProducao.IF, linha, coluna));
                break;
            case "then":
                this.tokens.add(new Token("THEN ->", valor, TipoProducao.THEN, linha, coluna));
                break;
            case "repeat":
                this.tokens.add(new Token("REAPEAT ->", valor, TipoProducao.REAPEAT, linha, coluna));
                break;
            case "until":
                this.tokens.add(new Token("UNTIL ->", valor, TipoProducao.UNTIL, linha, coluna));
                break;
            case "while":
                this.tokens.add(new Token("WHILE ->", valor, TipoProducao.WHILE, linha, coluna));
                break;
            case "do":
                this.tokens.add(new Token("DO ->", valor, TipoProducao.DO, linha, coluna));
                break;
            case "for":
                this.tokens.add(new Token("FOR ->", valor, TipoProducao.FOR, linha, coluna));
                break;
            case "else":
                this.tokens.add(new Token("ELSE ->", valor, TipoProducao.ELSE, linha, coluna));
                break;
            case "to":
                this.tokens.add(new Token("TO ->", valor, TipoProducao.TO, linha, coluna));
                break;
            case "downto":
                this.tokens.add(new Token("DOWNTO ->", valor, TipoProducao.DOWNTO, linha, coluna));
                break;
            default:
                this.tokens.add(new Token("IDENTIFICADOR ->", valor, TipoProducao.IDENTIFIER, linha, coluna));
                break;
        }

    }

}
